/**
 * 手写 call
 * @param {Object} ctx        要绑定的 this
 * @param {...any} restArgs   call 从第二个参数开始的所有实参
 * @returns {any}             原函数执行后的返回值
 */
Function.prototype.myCall = function (ctx) {
  /* 1. 处理 ctx 为 null / undefined 时指向全局对象 */
  if (ctx == null) {
    // 浏览器下为 window，Node 下为 globalThis
    ctx = typeof globalThis === 'object' ? globalThis : window;
  } else {
    /* 2. 将 ctx 转换为对象（基本类型 => 包装类） */
    ctx = Object(ctx);
  }

  /* 3. 生成唯一的 key，防止覆盖已有属性 */
  var fnKey = '__fn__' + Date.now();

  /* 4. 把当前函数（即 this）挂到 ctx 上，这样函数里的 this 就指向 ctx */
  ctx[fnKey] = this;

  /* 5. 收集实参（跳过第一个 ctx） */
  var args = [];
  for (var i = 1; i < arguments.length; i++) {
    args.push('arguments[' + i + ']');
  }

  /* 6. 利用 eval/new Function 执行函数，并传入参数
        这里用 eval 最直观，也可用 new Function('ctx', 'return ctx[fnKey](...)') */
  var result = eval('ctx[fnKey](' + args.join(',') + ')');

  /* 7. 删除临时属性，保持 ctx 干净 */
  delete ctx[fnKey];

  /* 8. 返回执行结果 */
  return result;
};

// 测试用例
function foo(a, b) {
  console.log(this.name, a + b);
  return a + b;
}

var obj = { name: 'Cat' };

// 原生 call
console.log(foo.call(obj, 3, 4)); // Cat 7   -> 7

// 手写 myCall
console.log(foo.myCall(obj, 3, 4)); // Cat 7   -> 7


/**
 * 原理说明
 * 谁调用了 myCall？
 * foo.myCall(obj, 3, 4);
 * 在 JavaScript 里，点前面的对象就是函数执行时的 this。
 * 所以 foo.myCall(...) 里的 this 不是 window，而是 foo 本身（那个函数）。
 * 同理 myCall中套壳了一个函数来改变this的指向到传入的对象中。
 * 最后删除这个套壳的函数就实现了改变this的效果
 */